#include <cairo.h>
#include <epoxy/gl.h>
-/* Parameters for our cache eviction strategy.
+/* Cache eviction strategy
*
- * Each cached glyph has an age that gets reset every time a cached glyph gets used.
- * Glyphs that have not been used for the MAX_AGE frames are considered old. We keep
- * count of the pixels of each atlas that are taken up by old glyphs. We check the
- * fraction of old pixels every CHECK_INTERVAL frames, and if it is above MAX_OLD_RATIO, then
- * we drop the atlas an all the glyphs contained in it from the cache.
+ * Each cached glyph has an age that gets reset every time a cached
+ * glyph gets used. Glyphs that have not been used for the
+ * MAX_FRAME_AGE frames are considered old.
+ *
+ * We keep count of the pixels of each atlas that are taken up by old
+ * data. When the fraction of old pixels gets too high, we drop the
+ * atlas and all the items it contained.
*/
-#define MAX_AGE 60
-#define CHECK_INTERVAL 10
-#define MAX_OLD_RATIO 0.333
-
-#define ATLAS_SIZE 512
+#define MAX_FRAME_AGE (5 * 60)
static guint glyph_cache_hash (gconstpointer v);
static gboolean glyph_cache_equal (gconstpointer v1,
static void glyph_cache_key_free (gpointer v);
static void glyph_cache_value_free (gpointer v);
-static GskGLTextureAtlas *
-create_atlas (GskGLGlyphCache *self,
- int width,
- int height)
+GskGLGlyphCache *
+gsk_gl_glyph_cache_new (GdkDisplay *display,
+ GskGLTextureAtlases *atlases)
{
- GskGLTextureAtlas *atlas;
+ GskGLGlyphCache *glyph_cache;
- atlas = g_new (GskGLTextureAtlas, 1);
- gsk_gl_texture_atlas_init (atlas, MAX (width, ATLAS_SIZE), MAX (height, ATLAS_SIZE));
+ glyph_cache = g_new0 (GskGLGlyphCache, 1);
- GSK_NOTE(GLYPH_CACHE, g_message ("Create atlas %d x %d", atlas->width, atlas->height));
-
- return atlas;
-}
+ glyph_cache->display = display;
+ glyph_cache->hash_table = g_hash_table_new_full (glyph_cache_hash, glyph_cache_equal,
+ glyph_cache_key_free, glyph_cache_value_free);
-static void
-free_atlas (gpointer v)
-{
- GskGLTextureAtlas *atlas = v;
+ glyph_cache->atlases = gsk_gl_texture_atlases_ref (atlases);
- g_assert (atlas->image.texture_id == 0);
- gsk_gl_texture_atlas_free (atlas);
+ glyph_cache->ref_count = 1;
- g_free (atlas);
+ return glyph_cache;
}
-void
-gsk_gl_glyph_cache_init (GskGLGlyphCache *self)
+GskGLGlyphCache *
+gsk_gl_glyph_cache_ref (GskGLGlyphCache *self)
{
- self->hash_table = g_hash_table_new_full (glyph_cache_hash, glyph_cache_equal,
- glyph_cache_key_free, glyph_cache_value_free);
- self->atlases = g_ptr_array_new_with_free_func (free_atlas);
+ self->ref_count++;
+
+ return self;
}
void
-gsk_gl_glyph_cache_free (GskGLGlyphCache *self,
- GskGLDriver *driver)
+gsk_gl_glyph_cache_unref (GskGLGlyphCache *self)
{
- guint i;
+ g_assert (self->ref_count > 0);
- for (i = 0; i < self->atlases->len; i ++)
+ if (self->ref_count == 1)
{
- GskGLTextureAtlas *atlas = g_ptr_array_index (self->atlases, i);
-
- if (atlas->image.texture_id != 0)
- {
- gsk_gl_image_destroy (&atlas->image, driver);
- atlas->image.texture_id = 0;
- }
+ gsk_gl_texture_atlases_unref (self->atlases);
+ g_hash_table_unref (self->hash_table);
+ g_free (self);
+ return;
}
- g_ptr_array_unref (self->atlases);
- g_hash_table_unref (self->hash_table);
+ self->ref_count--;
}
static gboolean
g_free (v);
}
-static void
-add_to_cache (GskGLGlyphCache *cache,
- GlyphCacheKey *key,
- GskGLCachedGlyph *value)
-{
- const int width = value->draw_width * key->scale / 1024;
- const int height = value->draw_height * key->scale / 1024;
- GskGLTextureAtlas *atlas = NULL;
- guint i, p;
- int packed_x, packed_y;
-
- /* Try all the atlases and pick the first one that can hold
- * our new glyph */
- for (i = 0, p = cache->atlases->len; i < p; i ++)
- {
- GskGLTextureAtlas *test_atlas = g_ptr_array_index (cache->atlases, i);
- gboolean was_packed;
-
- was_packed = gsk_gl_texture_atlas_pack (test_atlas, width, height, &packed_x, &packed_y);
-
- if (was_packed)
- {
- atlas = test_atlas;
- break;
- }
- }
-
- if (atlas == NULL)
- {
- gboolean was_packed;
-
- atlas = create_atlas (cache, width + 2, height + 2);
-
- g_ptr_array_add (cache->atlases, atlas);
-
- was_packed = gsk_gl_texture_atlas_pack (atlas,
- width + 2, height + 2,
- &packed_x, &packed_y);
-
- g_assert (was_packed);
- }
-
- value->tx = (float)(packed_x + 1) / atlas->width;
- value->ty = (float)(packed_y + 1) / atlas->height;
- value->tw = (float)width / atlas->width;
- value->th = (float)height / atlas->height;
- value->used = TRUE;
-
- value->atlas = atlas;
-
- if (atlas->user_data == NULL)
- atlas->user_data = g_new0 (DirtyGlyph, 1);
-
- ((DirtyGlyph *)atlas->user_data)->key = key;
- ((DirtyGlyph *)atlas->user_data)->value = value;
-
-#ifdef G_ENABLE_DEBUG
- if (GSK_DEBUG_CHECK (GLYPH_CACHE))
- {
- for (i = 0; i < cache->atlases->len; i++)
- {
- atlas = g_ptr_array_index (cache->atlases, i);
- g_message ("atlas %d (%dx%d): %.2g%% old pixels",
- i, atlas->width, atlas->height,
- gsk_gl_texture_atlas_get_unused_ratio (atlas));
- }
- }
-#endif
-}
-
static gboolean
-render_glyph (const GskGLTextureAtlas *atlas,
- const DirtyGlyph *glyph,
- GskImageRegion *region)
+render_glyph (GlyphCacheKey *key,
+ GskGLCachedGlyph *value,
+ GskImageRegion *region)
{
- GlyphCacheKey *key = glyph->key;
- GskGLCachedGlyph *value = glyph->value;
cairo_surface_t *surface;
cairo_t *cr;
cairo_scaled_font_t *scaled_font;
scaled_font = pango_cairo_font_get_scaled_font ((PangoCairoFont *)key->font);
if (G_UNLIKELY (!scaled_font || cairo_scaled_font_status (scaled_font) != CAIRO_STATUS_SUCCESS))
- return FALSE;
+ {
+ g_warning ("Failed to get a font");
+ return FALSE;
+ }
surface_width = value->draw_width * key->scale / 1024;
surface_height = value->draw_height * key->scale / 1024;
- /* TODO: Give glyphs that large their own texture in the proper size. Don't
- * put them in the atlas at all. */
- if (surface_width > atlas->width || surface_height > atlas->height)
- return FALSE;
-
stride = cairo_format_stride_for_width (CAIRO_FORMAT_ARGB32, surface_width);
data = g_malloc0 (stride * surface_height);
surface = cairo_image_surface_create_for_data (data, CAIRO_FORMAT_ARGB32,
region->height = cairo_image_surface_get_height (surface);
region->stride = cairo_image_surface_get_stride (surface);
region->data = data;
- region->x = (gsize)(value->tx * atlas->width);
- region->y = (gsize)(value->ty * atlas->height);
+ if (value->atlas)
+ {
+ region->x = (gsize)(value->tx * value->atlas->width);
+ region->y = (gsize)(value->ty * value->atlas->height);
+ }
+ else
+ {
+ region->x = 0;
+ region->y = 0;
+ }
cairo_surface_destroy (surface);
+
return TRUE;
}
static void
-upload_dirty_glyph (GskGLGlyphCache *self,
- GskGLTextureAtlas *atlas,
- GskGLDriver *driver)
+upload_glyph (GlyphCacheKey *key,
+ GskGLCachedGlyph *value)
{
- GskImageRegion region;
-
- g_assert (atlas->user_data != NULL);
+ GskImageRegion r;
- gdk_gl_context_push_debug_group_printf (gsk_gl_driver_get_gl_context (driver),
- "Uploading glyph %d", ((DirtyGlyph *)atlas->user_data)->key->glyph);
+ gdk_gl_context_push_debug_group_printf (gdk_gl_context_get_current (),
+ "Uploading glyph %d",
+ key->glyph);
- if (render_glyph (atlas, (DirtyGlyph *)atlas->user_data, ®ion))
+ if (render_glyph (key, value, &r))
{
+ glBindTexture (GL_TEXTURE_2D, value->texture_id);
+ glTextureSubImage2D (value->texture_id, 0,
+ r.x, r.y, r.width, r.height,
+ GL_BGRA, GL_UNSIGNED_INT_8_8_8_8_REV,
+ r.data);
+ g_free (r.data);
+ }
- gsk_gl_image_upload_region (&atlas->image, driver, ®ion);
+ gdk_gl_context_pop_debug_group (gdk_gl_context_get_current ());
+}
- g_free (region.data);
- }
+static void
+add_to_cache (GskGLGlyphCache *self,
+ GlyphCacheKey *key,
+ GskGLCachedGlyph *value)
+{
+ const int width = value->draw_width * key->scale / 1024;
+ const int height = value->draw_height * key->scale / 1024;
+ GskGLTextureAtlas *atlas = NULL;
+ int packed_x = 0;
+ int packed_y = 0;
+
+ gsk_gl_texture_atlases_pack (self->atlases, width + 2, height + 2, &atlas, &packed_x, &packed_y);
+
+ value->tx = (float)(packed_x + 1) / atlas->width;
+ value->ty = (float)(packed_y + 1) / atlas->height;
+ value->tw = (float)width / atlas->width;
+ value->th = (float)height / atlas->height;
+ value->used = TRUE;
+
+ value->atlas = atlas;
+ value->texture_id = atlas->texture_id;
+
+ upload_glyph (key, value);
+}
+
+void
+gsk_gl_glyph_cache_get_texture (GskGLDriver *driver,
+ PangoFont *font,
+ PangoGlyph glyph,
+ float scale,
+ GskGLCachedGlyph *value)
+{
+ PangoRectangle ink_rect;
+ GlyphCacheKey key;
+ int width, height;
+ guint texture_id;
+
+ pango_font_get_glyph_extents (font, glyph, &ink_rect, NULL);
+ pango_extents_to_pixels (&ink_rect, NULL);
+
+ key.font = font;
+ key.glyph = glyph;
+ key.scale = (guint)(scale * 1024);
+
+ value->atlas = NULL;
+ value->timestamp = 0;
+
+ value->draw_x = ink_rect.x;
+ value->draw_y = ink_rect.y;
+ value->draw_width = ink_rect.width;
+ value->draw_height = ink_rect.height;
+
+ value->tx = 0.0f;
+ value->ty = 0.0f;
+ value->tw = 1.0f;
+ value->th = 1.0f;
+
+ width = value->draw_width * key.scale / 1024;
+ height = value->draw_height * key.scale / 1024;
+
+ texture_id = gsk_gl_driver_create_texture (driver, width, height);
+ gsk_gl_driver_bind_source_texture (driver, texture_id);
+ gsk_gl_driver_init_texture_empty (driver, texture_id, GL_NEAREST, GL_NEAREST);
- gdk_gl_context_pop_debug_group (gsk_gl_driver_get_gl_context (driver));
- /* TODO: This could be unnecessary. We can just reuse the allocated
- * DirtyGlyph next time. */
- g_clear_pointer (&atlas->user_data, g_free);
+ value->texture_id = texture_id;
+
+ upload_glyph (&key, value);
}
-const GskGLCachedGlyph *
-gsk_gl_glyph_cache_lookup (GskGLGlyphCache *cache,
- gboolean create,
- PangoFont *font,
- PangoGlyph glyph,
- float scale)
+gboolean
+gsk_gl_glyph_cache_lookup (GskGLGlyphCache *cache,
+ PangoFont *font,
+ PangoGlyph glyph,
+ float scale,
+ GskGLCachedGlyph *cached_glyph_out)
{
GskGLCachedGlyph *value;
{
const guint age = cache->timestamp - value->timestamp;
- if (MAX_AGE <= age)
+ if (age > MAX_FRAME_AGE)
{
GskGLTextureAtlas *atlas = value->atlas;
value->timestamp = cache->timestamp;
}
- if (create && value == NULL)
+ if (value == NULL)
{
- GlyphCacheKey *key;
PangoRectangle ink_rect;
- key = g_new0 (GlyphCacheKey, 1);
- value = g_new0 (GskGLCachedGlyph, 1);
-
pango_font_get_glyph_extents (font, glyph, &ink_rect, NULL);
pango_extents_to_pixels (&ink_rect, NULL);
+ value = g_new0 (GskGLCachedGlyph, 1);
+
value->draw_x = ink_rect.x;
value->draw_y = ink_rect.y;
value->draw_width = ink_rect.width;
value->draw_height = ink_rect.height;
value->timestamp = cache->timestamp;
value->atlas = NULL; /* For now */
- value->scale = (guint)(scale * 1024);
-
- key->font = g_object_ref (font);
- key->glyph = glyph;
- key->scale = (guint)(scale * 1024);
- if (ink_rect.width > 0 && ink_rect.height > 0 && key->scale > 0)
- add_to_cache (cache, key, value);
-
- g_hash_table_insert (cache->hash_table, key, value);
- }
+ if (ink_rect.width < 128 && ink_rect.height < 128)
+ {
+ GlyphCacheKey *key;
- return value;
-}
+ key = g_new0 (GlyphCacheKey, 1);
-guint
-gsk_gl_glyph_cache_get_glyph_texture_id (GskGLGlyphCache *self,
- GskGLDriver *driver,
- const GskGLCachedGlyph *glyph)
-{
- GskGLTextureAtlas *atlas = glyph->atlas;
+ key->font = g_object_ref (font);
+ key->glyph = glyph;
+ key->scale = (guint)(scale * 1024);
- g_assert (atlas != NULL);
+ if (ink_rect.width > 0 && ink_rect.height > 0 && key->scale > 0)
+ add_to_cache (cache, key, value);
- if (atlas->image.texture_id == 0)
+ *cached_glyph_out = *value;
+ g_hash_table_insert (cache->hash_table, key, value);
+ }
+ else
+ {
+ *cached_glyph_out = *value;
+ glyph_cache_value_free (value);
+ }
+ }
+ else
{
- gsk_gl_image_create (&atlas->image, driver, atlas->width, atlas->height, GL_LINEAR, GL_LINEAR);
- gdk_gl_context_label_object_printf (gsk_gl_driver_get_gl_context (driver),
- GL_TEXTURE, atlas->image.texture_id,
- "Glyph atlas %d", atlas->image.texture_id);
+ *cached_glyph_out = *value;
}
- if (atlas->user_data != NULL)
- upload_dirty_glyph (self, atlas, driver);
-
- return atlas->image.texture_id;
+ return cached_glyph_out->atlas != NULL;
}
void
-gsk_gl_glyph_cache_begin_frame (GskGLGlyphCache *self,
- GskGLDriver *driver)
+gsk_gl_glyph_cache_begin_frame (GskGLGlyphCache *self)
{
- int i;
GHashTableIter iter;
GlyphCacheKey *key;
GskGLCachedGlyph *value;
- GHashTable *removed = g_hash_table_new (g_direct_hash, g_direct_equal);
guint dropped = 0;
self->timestamp++;
- if ((self->timestamp - 1) % CHECK_INTERVAL != 0)
- return;
-
- /* look for atlases to drop, and create a mapping of updated texture indices */
- for (i = self->atlases->len - 1; i >= 0; i--)
- {
- GskGLTextureAtlas *atlas = g_ptr_array_index (self->atlases, i);
-
- if (gsk_gl_texture_atlas_get_unused_ratio (atlas) > MAX_OLD_RATIO)
- {
- GSK_NOTE(GLYPH_CACHE,
- g_message ("Dropping atlas %d (%g.2%% old)", i,
- gsk_gl_texture_atlas_get_unused_ratio (atlas)));
-
-#if 0
- static int kk;
-
- g_message ("Dropping glyph cache... Ratio: %f",
- gsk_gl_texture_atlas_get_unused_ratio (atlas));
- gsk_gl_image_write_to_png (&atlas->image, driver,
- g_strdup_printf ("dropped_%d.png", kk++));
-#endif
-
- if (atlas->image.texture_id != 0)
- {
- gsk_gl_image_destroy (&atlas->image, driver);
- atlas->image.texture_id = 0;
- }
-
- g_hash_table_add (removed, atlas);
-
- g_ptr_array_remove_index (self->atlases, i);
- }
- }
-
- /* Remove all glyphs whose atlas was removed, and
- * mark old glyphs as unused
- */
g_hash_table_iter_init (&iter, self->hash_table);
while (g_hash_table_iter_next (&iter, (gpointer *)&key, (gpointer *)&value))
{
- if (g_hash_table_contains (removed, value->atlas))
+ guint pos;
+
+ if (!g_ptr_array_find (self->atlases->atlases, value->atlas, &pos))
{
g_hash_table_iter_remove (&iter);
dropped++;
{
const guint age = self->timestamp - value->timestamp;
- if (MAX_AGE <= age && age < MAX_AGE + CHECK_INTERVAL)
+ if (age > MAX_FRAME_AGE)
{
GskGLTextureAtlas *atlas = value->atlas;
}
}
}
- g_hash_table_unref (removed);
GSK_NOTE(GLYPH_CACHE, if (dropped > 0) g_message ("Dropped %d glyphs", dropped));
-
-#if 0
- for (i = 0; i < self->atlases->len; i++)
- {
- GskGLGlyphAtlas *atlas = g_ptr_array_index (self->atlases, i);
-
- if (atlas->image)
- {
- char *filename;
-
- filename = g_strdup_printf ("glyphatlas%d-%ld.png", i, self->timestamp);
- gsk_gl_image_write_to_png (atlas->image, driver, filename);
- g_free (filename);
- }
- }
-#endif
}
typedef struct
{
+ int ref_count;
+
+ GdkDisplay *display;
GHashTable *hash_table;
- GPtrArray *atlases;
+ GskGLTextureAtlases *atlases;
guint64 timestamp;
} GskGLGlyphCache;
guint scale; /* times 1024 */
} GlyphCacheKey;
-typedef struct _DirtyGlyph DirtyGlyph;
typedef struct _GskGLCachedGlyph GskGLCachedGlyph;
-struct _DirtyGlyph
-{
- GlyphCacheKey *key;
- GskGLCachedGlyph *value;
-};
-
struct _GskGLCachedGlyph
{
GskGLTextureAtlas *atlas;
+ guint texture_id;
float tx;
float ty;
int draw_width;
int draw_height;
- float scale;
-
guint64 timestamp;
guint used: 1;
};
-void gsk_gl_glyph_cache_init (GskGLGlyphCache *self);
-void gsk_gl_glyph_cache_free (GskGLGlyphCache *self,
- GskGLDriver *driver);
-void gsk_gl_glyph_cache_begin_frame (GskGLGlyphCache *self,
- GskGLDriver *driver);
-guint gsk_gl_glyph_cache_get_glyph_texture_id (GskGLGlyphCache *self,
- GskGLDriver *driver,
- const GskGLCachedGlyph *glyph);
-const GskGLCachedGlyph * gsk_gl_glyph_cache_lookup (GskGLGlyphCache *self,
- gboolean create,
+GskGLGlyphCache * gsk_gl_glyph_cache_new (GdkDisplay *display,
+ GskGLTextureAtlases *atlases);
+GskGLGlyphCache * gsk_gl_glyph_cache_ref (GskGLGlyphCache *self);
+void gsk_gl_glyph_cache_unref (GskGLGlyphCache *self);
+void gsk_gl_glyph_cache_begin_frame (GskGLGlyphCache *self);
+gboolean gsk_gl_glyph_cache_lookup (GskGLGlyphCache *self,
PangoFont *font,
PangoGlyph glyph,
- float scale);
+ float scale,
+ GskGLCachedGlyph *cached_glyph_out);
+void gsk_gl_glyph_cache_get_texture (GskGLDriver *driver,
+ PangoFont *font,
+ PangoGlyph glyph,
+ float scale,
+ GskGLCachedGlyph *glyph_out);
#endif
#include "gskgliconcacheprivate.h"
#include "gskgltextureatlasprivate.h"
#include "gdk/gdktextureprivate.h"
+#include "gdk/gdkglcontextprivate.h"
#include <epoxy/gl.h>
-#define ATLAS_SIZE (1024)
#define MAX_FRAME_AGE (5 * 60)
-#define MAX_UNUSED_RATIO 0.8
typedef struct
{
g_free (p);
}
-static void
-free_atlas (gpointer v)
+GskGLIconCache *
+gsk_gl_icon_cache_new (GdkDisplay *display,
+ GskGLTextureAtlases *atlases)
{
- GskGLTextureAtlas *atlas = v;
+ GskGLIconCache *self;
+
+ self = g_new0 (GskGLIconCache, 1);
- g_assert (atlas->image.texture_id == 0);
- gsk_gl_texture_atlas_free (atlas);
+ self->display = display;
+ self->icons = g_hash_table_new_full (NULL, NULL, NULL, icon_data_free);
+ self->atlases = gsk_gl_texture_atlases_ref (atlases);
+ self->ref_count = 1;
- g_free (atlas);
+ return self;
}
-void
-gsk_gl_icon_cache_init (GskGLIconCache *self,
- GskRenderer *renderer,
- GskGLDriver *gl_driver)
+GskGLIconCache *
+gsk_gl_icon_cache_ref (GskGLIconCache *self)
{
- self->renderer = renderer;
- self->gl_driver = gl_driver;
+ self->ref_count++;
- self->atlases = g_ptr_array_new_with_free_func ((GDestroyNotify)free_atlas);
- self->icons = g_hash_table_new_full (NULL, NULL, NULL, icon_data_free);
+ return self;
}
void
-gsk_gl_icon_cache_free (GskGLIconCache *self)
+gsk_gl_icon_cache_unref (GskGLIconCache *self)
{
- guint i, p;
+ g_assert (self->ref_count > 0);
- for (i = 0, p = self->atlases->len; i < p; i ++)
+ if (self->ref_count == 1)
{
- GskGLTextureAtlas *atlas = g_ptr_array_index (self->atlases, i);
-
- if (atlas->image.texture_id != 0)
- {
- gsk_gl_image_destroy (&atlas->image, self->gl_driver);
- atlas->image.texture_id = 0;
- }
-
- gsk_gl_texture_atlas_free (atlas);
-
- g_free (atlas);
+ gsk_gl_texture_atlases_unref (self->atlases);
+ g_hash_table_unref (self->icons);
+ g_free (self);
+ return;
}
- g_ptr_array_free (self->atlases, TRUE);
- g_hash_table_unref (self->icons);
+ self->ref_count--;
}
void
gsk_gl_icon_cache_begin_frame (GskGLIconCache *self)
{
- gint i, p;
GHashTableIter iter;
GdkTexture *texture;
IconData *icon_data;
g_hash_table_iter_init (&iter, self->icons);
while (g_hash_table_iter_next (&iter, (gpointer *)&texture, (gpointer *)&icon_data))
{
- icon_data->frame_age ++;
+ guint pos;
- if (icon_data->frame_age > MAX_FRAME_AGE)
+ if (!g_ptr_array_find (self->atlases->atlases, icon_data->atlas, &pos))
{
-
- if (icon_data->used)
- {
- const int w = icon_data->texture_rect.size.width * ATLAS_SIZE;
- const int h = icon_data->texture_rect.size.height * ATLAS_SIZE;
-
- gsk_gl_texture_atlas_mark_unused (icon_data->atlas, w + 2, h + 2);
- icon_data->used = FALSE;
- }
- /* We do NOT remove the icon here. Instead, We wait until we drop the entire atlas.
- * This way we can revive it when we use it again. */
+ g_hash_table_iter_remove (&iter);
}
- }
-
- for (i = 0, p = self->atlases->len; i < p; i ++)
- {
- GskGLTextureAtlas *atlas = g_ptr_array_index (self->atlases, i);
-
- if (gsk_gl_texture_atlas_get_unused_ratio (atlas) > MAX_UNUSED_RATIO)
+ else
{
- g_hash_table_iter_init (&iter, self->icons);
- while (g_hash_table_iter_next (&iter, (gpointer *)&texture, (gpointer *)&icon_data))
- {
- if (icon_data->atlas == atlas)
- g_hash_table_iter_remove (&iter);
- }
+ icon_data->frame_age ++;
- if (atlas->image.texture_id != 0)
+ if (icon_data->frame_age > MAX_FRAME_AGE)
{
- gsk_gl_image_destroy (&atlas->image, self->gl_driver);
- atlas->image.texture_id = 0;
- }
- g_ptr_array_remove_index_fast (self->atlases, i);
- i --; /* Check the current index again */
+ if (icon_data->used)
+ {
+ const int w = icon_data->texture_rect.size.width * icon_data->atlas->width;
+ const int h = icon_data->texture_rect.size.height * icon_data->atlas->height;
+
+ gsk_gl_texture_atlas_mark_unused (icon_data->atlas, w + 2, h + 2);
+ icon_data->used = FALSE;
+ }
+ /* We do NOT remove the icon here. Instead, We wait until we drop the entire atlas.
+ * This way we can revive it when we use it again. */
+ }
}
}
}
return padded;
}
+static void
+upload_region_or_else (GskGLIconCache *self,
+ guint texture_id,
+ GskImageRegion *region)
+{
+ glBindTexture (GL_TEXTURE_2D, texture_id);
+ glTextureSubImage2D (texture_id, 0, region->x, region->y, region->width, region->height,
+ GL_BGRA, GL_UNSIGNED_INT_8_8_8_8_REV, region->data);
+}
+
void
gsk_gl_icon_cache_lookup_or_add (GskGLIconCache *self,
GdkTexture *texture,
icon_data->frame_age = 0;
if (!icon_data->used)
{
- const int w = icon_data->texture_rect.size.width * ATLAS_SIZE;
- const int h = icon_data->texture_rect.size.height * ATLAS_SIZE;
+ const int w = icon_data->texture_rect.size.width * icon_data->atlas->width;
+ const int h = icon_data->texture_rect.size.height * icon_data->atlas->height;
gsk_gl_texture_atlas_mark_used (icon_data->atlas, w + 2, h + 2);
icon_data->used = TRUE;
}
- *out_texture_id = icon_data->atlas->image.texture_id;
+ *out_texture_id = icon_data->atlas->texture_id;
*out_texture_rect = icon_data->texture_rect;
return;
}
/* texture not on any atlas yet. Find a suitable one. */
{
- const int twidth = gdk_texture_get_width (texture);
- const int theight = gdk_texture_get_height (texture);
- int packed_x, packed_y;
+ const int width = gdk_texture_get_width (texture);
+ const int height = gdk_texture_get_height (texture);
GskGLTextureAtlas *atlas = NULL;
- guint i, p;
+ int packed_x = 0;
+ int packed_y = 0;
GskImageRegion region;
cairo_surface_t *surface;
cairo_surface_t *padded_surface;
- g_assert (twidth < ATLAS_SIZE);
- g_assert (theight < ATLAS_SIZE);
-
- for (i = 0, p = self->atlases->len; i < p; i ++)
- {
- atlas = g_ptr_array_index (self->atlases, i);
-
- if (gsk_gl_texture_atlas_pack (atlas, twidth + 2, theight + 2, &packed_x, &packed_y))
- {
- packed_x += 1;
- packed_y += 1;
- break;
- }
-
- atlas = NULL;
- }
-
- if (!atlas)
- {
- /* No atlas has enough space, so create a new one... */
- atlas = g_malloc (sizeof (GskGLTextureAtlas));
- gsk_gl_texture_atlas_init (atlas, ATLAS_SIZE, ATLAS_SIZE);
- gsk_gl_image_create (&atlas->image, self->gl_driver, atlas->width, atlas->height, GL_LINEAR, GL_LINEAR);
- /* Pack it onto that one, which surely has enought space... */
- gsk_gl_texture_atlas_pack (atlas, twidth + 2, theight + 2, &packed_x, &packed_y);
- packed_x += 1;
- packed_y += 1;
-
- g_ptr_array_add (self->atlases, atlas);
- }
+ gsk_gl_texture_atlases_pack (self->atlases, width + 2, height + 2, &atlas, &packed_x, &packed_y);
icon_data = g_new0 (IconData, 1);
icon_data->atlas = atlas;
icon_data->frame_age = 0;
icon_data->used = TRUE;
graphene_rect_init (&icon_data->texture_rect,
- (float)packed_x / ATLAS_SIZE,
- (float)packed_y / ATLAS_SIZE,
- (float)twidth / ATLAS_SIZE,
- (float)theight / ATLAS_SIZE);
+ (float)(packed_x + 1) / atlas->width,
+ (float)(packed_y + 1) / atlas->height,
+ (float)width / atlas->width,
+ (float)height / atlas->height);
g_hash_table_insert (self->icons, texture, icon_data);
/* actually upload the texture */
surface = gdk_texture_download_surface (texture);
padded_surface = pad_surface (surface);
- region.x = packed_x - 1;
- region.y = packed_y - 1;
- region.width = twidth + 2;
- region.height = theight + 2;
+ region.x = packed_x;
+ region.y = packed_y;
+ region.width = width + 2;
+ region.height = height + 2;
region.data = cairo_image_surface_get_data (padded_surface);
- gsk_gl_image_upload_region (&atlas->image, self->gl_driver, ®ion);
+ gdk_gl_context_push_debug_group_printf (gdk_gl_context_get_current (),
+ "Uploading texture");
+
+ upload_region_or_else (self, atlas->texture_id, ®ion);
+
+ gdk_gl_context_pop_debug_group (gdk_gl_context_get_current ());
- *out_texture_id = atlas->image.texture_id;
+ *out_texture_id = atlas->texture_id;
*out_texture_rect = icon_data->texture_rect;
cairo_surface_destroy (surface);
typedef struct
{
+ int ref_count;
+
+ GdkDisplay *display;
GskGLDriver *gl_driver;
- GskRenderer *renderer;
- GPtrArray *atlases;
+ GskGLTextureAtlases *atlases;
GHashTable *icons; /* GdkTexture -> IconData */
} GskGLIconCache;
-void gsk_gl_icon_cache_init (GskGLIconCache *self,
- GskRenderer *renderer,
- GskGLDriver *gl_driver);
-void gsk_gl_icon_cache_free (GskGLIconCache *self);
+GskGLIconCache * gsk_gl_icon_cache_new (GdkDisplay *display,
+ GskGLTextureAtlases *atlases);
+GskGLIconCache * gsk_gl_icon_cache_ref (GskGLIconCache *self);
+void gsk_gl_icon_cache_unref (GskGLIconCache *self);
void gsk_gl_icon_cache_begin_frame (GskGLIconCache *self);
void gsk_gl_icon_cache_lookup_or_add (GskGLIconCache *self,
GdkTexture *texture,
RenderOpBuilder op_builder;
GArray *render_ops;
- GskGLGlyphCache glyph_cache;
- GskGLIconCache icon_cache;
+ GskGLTextureAtlases *atlases;
+ GskGLGlyphCache *glyph_cache;
+ GskGLIconCache *icon_cache;
GskGLShadowCache shadow_cache;
#ifdef G_ENABLE_DEBUG
for (i = 0; i < num_glyphs; i++)
{
const PangoGlyphInfo *gi = &glyphs[i];
- const GskGLCachedGlyph *glyph;
+ GskGLCachedGlyph glyph;
float glyph_x, glyph_y, glyph_w, glyph_h;
float tx, ty, tx2, ty2;
double cx;
if (gi->glyph == PANGO_GLYPH_EMPTY)
continue;
- glyph = gsk_gl_glyph_cache_lookup (&self->glyph_cache,
- TRUE,
- (PangoFont *)font,
- gi->glyph,
- text_scale);
+ gsk_gl_glyph_cache_lookup (self->glyph_cache,
+ (PangoFont *)font,
+ gi->glyph,
+ text_scale,
+ &glyph);
/* e.g. whitespace */
- if (glyph->draw_width <= 0 || glyph->draw_height <= 0 || glyph->scale <= 0)
+ if (glyph.draw_width <= 0 || glyph.draw_height <= 0)
goto next;
+ /* big glyphs are not cached */
+ if (!glyph.texture_id)
+ {
+ gsk_gl_glyph_cache_get_texture (self->gl_driver,
+ (PangoFont *)font,
+ gi->glyph,
+ text_scale,
+ &glyph);
+ g_assert (glyph.texture_id != 0);
+ }
+
cx = (double)(x_position + gi->geometry.x_offset) / PANGO_SCALE;
cy = (double)(gi->geometry.y_offset) / PANGO_SCALE;
- ops_set_texture (builder, gsk_gl_glyph_cache_get_glyph_texture_id (&self->glyph_cache,
- self->gl_driver,
- glyph));
+ ops_set_texture (builder, glyph.texture_id);
- tx = glyph->tx;
- ty = glyph->ty;
- tx2 = tx + glyph->tw;
- ty2 = ty + glyph->th;
+ tx = glyph.tx;
+ ty = glyph.ty;
+ tx2 = tx + glyph.tw;
+ ty2 = ty + glyph.th;
- glyph_x = x + cx + glyph->draw_x;
- glyph_y = y + cy + glyph->draw_y;
- glyph_w = glyph->draw_width;
- glyph_h = glyph->draw_height;
+ glyph_x = x + cx + glyph.draw_x;
+ glyph_y = y + cy + glyph.draw_y;
+ glyph_w = glyph.draw_width;
+ glyph_h = glyph.draw_height;
ops_draw (builder, (GskQuadVertex[GL_N_VERTICES]) {
{ { glyph_x, glyph_y }, { tx, ty }, },
int texture_id;
float tx = 0, ty = 0, tx2 = 1, ty2 = 1;
- if (texture->width <= 64 &&
- texture->height <= 64)
+ if (texture->width <= 128 &&
+ texture->height <= 128)
{
graphene_rect_t trect;
- gsk_gl_icon_cache_lookup_or_add (&self->icon_cache,
+ gsk_gl_icon_cache_lookup_or_add (self->icon_cache,
texture,
&texture_id,
&trect);
return TRUE;
}
+static GskGLTextureAtlases *
+get_texture_atlases_for_display (GdkDisplay *display)
+{
+ GskGLTextureAtlases *atlases;
+
+ if (g_getenv ("GSK_NO_SHARED_CACHES"))
+ return gsk_gl_texture_atlases_new ();
+
+ atlases = (GskGLTextureAtlases*)g_object_get_data (G_OBJECT (display), "gsk-gl-texture-atlases");
+ if (atlases == NULL)
+ {
+ atlases = gsk_gl_texture_atlases_new ();
+ g_object_set_data_full (G_OBJECT (display), "gsk-gl-texture-atlases",
+ gsk_gl_texture_atlases_ref (atlases),
+ (GDestroyNotify) gsk_gl_texture_atlases_unref);
+ }
+
+ return atlases;
+}
+
+static GskGLGlyphCache *
+get_glyph_cache_for_display (GdkDisplay *display,
+ GskGLTextureAtlases *atlases)
+{
+ GskGLGlyphCache *glyph_cache;
+
+ if (g_getenv ("GSK_NO_SHARED_CACHES"))
+ return gsk_gl_glyph_cache_new (display, atlases);
+
+ glyph_cache = (GskGLGlyphCache*)g_object_get_data (G_OBJECT (display), "gsk-gl-glyph-cache");
+ if (glyph_cache == NULL)
+ {
+ glyph_cache = gsk_gl_glyph_cache_new (display, atlases);
+ g_object_set_data_full (G_OBJECT (display), "gsk-gl-glyph-cache",
+ gsk_gl_glyph_cache_ref (glyph_cache),
+ (GDestroyNotify) gsk_gl_glyph_cache_unref);
+ }
+
+ return glyph_cache;
+}
+
+static GskGLIconCache *
+get_icon_cache_for_display (GdkDisplay *display,
+ GskGLTextureAtlases *atlases)
+{
+ GskGLIconCache *icon_cache;
+
+ if (g_getenv ("GSK_NO_SHARED_CACHES"))
+ return gsk_gl_icon_cache_new (display, atlases);
+
+ icon_cache = (GskGLIconCache*)g_object_get_data (G_OBJECT (display), "gsk-gl-icon-cache");
+ if (icon_cache == NULL)
+ {
+ icon_cache = gsk_gl_icon_cache_new (display, atlases);
+ g_object_set_data_full (G_OBJECT (display), "gsk-gl-icon-cache",
+ gsk_gl_icon_cache_ref (icon_cache),
+ (GDestroyNotify) gsk_gl_icon_cache_unref);
+ }
+
+ return icon_cache;
+}
+
static gboolean
gsk_gl_renderer_realize (GskRenderer *renderer,
GdkSurface *surface,
if (!gsk_gl_renderer_create_programs (self, error))
return FALSE;
- gsk_gl_glyph_cache_init (&self->glyph_cache);
- gsk_gl_icon_cache_init (&self->icon_cache, renderer, self->gl_driver);
+ self->atlases = get_texture_atlases_for_display (gdk_surface_get_display (surface));
+ self->glyph_cache = get_glyph_cache_for_display (gdk_surface_get_display (surface), self->atlases);
+ self->icon_cache = get_icon_cache_for_display (gdk_surface_get_display (surface), self->atlases);
gsk_gl_shadow_cache_init (&self->shadow_cache);
return TRUE;
for (i = 0; i < GL_N_PROGRAMS; i ++)
glDeleteProgram (self->programs[i].id);
- gsk_gl_glyph_cache_free (&self->glyph_cache, self->gl_driver);
- gsk_gl_icon_cache_free (&self->icon_cache);
+ g_clear_pointer (&self->glyph_cache, gsk_gl_glyph_cache_unref);
+ g_clear_pointer (&self->icon_cache, gsk_gl_icon_cache_unref);
+ g_clear_pointer (&self->atlases, gsk_gl_texture_atlases_unref);
gsk_gl_shadow_cache_free (&self->shadow_cache, self->gl_driver);
g_clear_object (&self->gl_profiler);
ORTHO_FAR_PLANE);
graphene_matrix_scale (&projection, 1, -1, 1);
- gsk_gl_glyph_cache_begin_frame (&self->glyph_cache, self->gl_driver);
- gsk_gl_icon_cache_begin_frame (&self->icon_cache);
+ gsk_gl_texture_atlases_begin_frame (self->atlases);
+ gsk_gl_glyph_cache_begin_frame (self->glyph_cache);
+ gsk_gl_icon_cache_begin_frame (self->icon_cache);
gsk_gl_shadow_cache_begin_frame (&self->shadow_cache, self->gl_driver);
ops_set_projection (&self->op_builder, &projection);
+#include "config.h"
#include "gskgltextureatlasprivate.h"
+#include "gskdebugprivate.h"
+#include "gdkglcontextprivate.h"
+#include <epoxy/gl.h>
+#define ATLAS_SIZE (512)
+#define MAX_OLD_RATIO 0.5
+
+static void
+free_atlas (gpointer v)
+{
+ GskGLTextureAtlas *atlas = v;
+
+ gsk_gl_texture_atlas_free (atlas);
+
+ g_free (atlas);
+}
+
+GskGLTextureAtlases *
+gsk_gl_texture_atlases_new (void)
+{
+ GskGLTextureAtlases *atlases;
+
+ atlases = g_new (GskGLTextureAtlases, 1);
+ atlases->atlases = g_ptr_array_new_with_free_func (free_atlas);
+
+ atlases->ref_count = 1;
+
+ return atlases;
+}
+
+GskGLTextureAtlases *
+gsk_gl_texture_atlases_ref (GskGLTextureAtlases *atlases)
+{
+ atlases->ref_count++;
+
+ return atlases;
+}
+
+void
+gsk_gl_texture_atlases_unref (GskGLTextureAtlases *atlases)
+{
+ g_assert (atlases->ref_count > 0);
+
+ if (atlases->ref_count == 1)
+ {
+ g_ptr_array_unref (atlases->atlases);
+ g_free (atlases);
+ return;
+ }
+
+ atlases->ref_count--;
+}
+
+#if 1
+static void
+write_atlas_to_png (GskGLTextureAtlas *atlas,
+ const char *filename)
+{
+ int stride = cairo_format_stride_for_width (CAIRO_FORMAT_ARGB32, atlas->width);
+ guchar *data = g_malloc (atlas->height * stride);
+ cairo_surface_t *s;
+
+ glBindTexture (GL_TEXTURE_2D, atlas->texture_id);
+ glGetTexImage (GL_TEXTURE_2D, 0, GL_BGRA, GL_UNSIGNED_INT_8_8_8_8_REV, data);
+ s = cairo_image_surface_create_for_data (data, CAIRO_FORMAT_ARGB32, atlas->width, atlas->height, stride);
+ cairo_surface_write_to_png (s, filename);
+
+ cairo_surface_destroy (s);
+ g_free (data);
+}
+#endif
+
+void
+gsk_gl_texture_atlases_begin_frame (GskGLTextureAtlases *atlases)
+{
+ int i;
+
+ for (i = atlases->atlases->len - 1; i >= 0; i--)
+ {
+ GskGLTextureAtlas *atlas = g_ptr_array_index (atlases->atlases, i);
+
+ if (gsk_gl_texture_atlas_get_unused_ratio (atlas) > MAX_OLD_RATIO)
+ {
+ GSK_NOTE(GLYPH_CACHE,
+ g_message ("Dropping atlas %d (%g.2%% old)", i,
+ gsk_gl_texture_atlas_get_unused_ratio (atlas)));
+
+ if (atlas->texture_id != 0)
+ {
+ glDeleteTextures (1, &atlas->texture_id);
+ atlas->texture_id = 0;
+ }
+
+ g_ptr_array_remove_index (atlases->atlases, i);
+ }
+ }
+
+#if 1
+ {
+ static guint timestamp;
+
+ timestamp++;
+ if (timestamp % 10 == 0)
+ for (i = 0; i < atlases->atlases->len; i++)
+ {
+ GskGLTextureAtlas *atlas = g_ptr_array_index (atlases->atlases, i);
+
+ if (atlas->texture_id)
+ {
+ char *filename;
+
+ filename = g_strdup_printf ("textureatlas%d-%u.png", i, timestamp);
+ write_atlas_to_png (atlas, filename);
+ g_free (filename);
+ }
+ }
+ }
+#endif
+}
+
+gboolean
+gsk_gl_texture_atlases_pack (GskGLTextureAtlases *atlases,
+ int width,
+ int height,
+ GskGLTextureAtlas **atlas_out,
+ int *out_x,
+ int *out_y)
+{
+ GskGLTextureAtlas *atlas;
+ int x, y;
+ int i;
+
+ g_assert (width < ATLAS_SIZE);
+ g_assert (height < ATLAS_SIZE);
+
+ for (i = 0; i < atlases->atlases->len; i++)
+ {
+ atlas = g_ptr_array_index (atlases->atlases, i);
+
+ if (gsk_gl_texture_atlas_pack (atlas, width, height, &x, &y))
+ break;
+
+ atlas = NULL;
+ }
+
+ if (atlas == NULL)
+ {
+ /* No atlas has enough space, so create a new one... */
+ atlas = g_malloc (sizeof (GskGLTextureAtlas));
+ gsk_gl_texture_atlas_init (atlas, ATLAS_SIZE, ATLAS_SIZE);
+ gsk_gl_texture_atlas_realize (atlas);
+ g_ptr_array_add (atlases->atlases, atlas);
+
+ /* Pack it onto that one, which surely has enough space... */
+ gsk_gl_texture_atlas_pack (atlas, width, height, &x, &y);
+ }
+
+ *atlas_out = atlas;
+ *out_x = x;
+ *out_y = y;
+
+ return TRUE;
+}
void
gsk_gl_texture_atlas_init (GskGLTextureAtlas *self,
{
memset (self, 0, sizeof (*self));
- self->image.texture_id = 0;
+ self->texture_id = 0;
self->width = width;
self->height = height;
width, height,
self->nodes,
width);
+
+ gsk_gl_texture_atlas_realize (self);
}
void
gsk_gl_texture_atlas_free (GskGLTextureAtlas *self)
{
+ if (self->texture_id != 0)
+ {
+ glDeleteTextures (1, &self->texture_id);
+ self->texture_id = 0;
+ }
+
g_clear_pointer (&self->nodes, g_free);
}
return 0.0;
}
+/* Not using gdk_gl_driver_create_texture here, since we want
+ * this texture to survive the driver and stay around until
+ * the display gets closed.
+ */
+static guint
+create_shared_texture (int width,
+ int height)
+{
+ guint texture_id;
+
+ glGenTextures (1, &texture_id);
+ glBindTexture (GL_TEXTURE_2D, texture_id);
+
+ glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
+ glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
+
+ glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
+ glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
+
+ if (gdk_gl_context_get_use_es (gdk_gl_context_get_current ()))
+ glTexImage2D (GL_TEXTURE_2D, 0, GL_RGBA8, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, NULL);
+ else
+ glTexImage2D (GL_TEXTURE_2D, 0, GL_RGBA8, width, height, 0, GL_BGRA, GL_UNSIGNED_BYTE, NULL);
+
+ glBindTexture (GL_TEXTURE_2D, 0);
+
+ return texture_id;
+}
+
+void
+gsk_gl_texture_atlas_realize (GskGLTextureAtlas *atlas)
+{
+ if (atlas->texture_id)
+ return;
+
+ atlas->texture_id = create_shared_texture (atlas->width, atlas->height);
+ gdk_gl_context_label_object_printf (gdk_gl_context_get_current (),
+ GL_TEXTURE, atlas->texture_id,
+ "Glyph atlas %d", atlas->texture_id);
+}
int width;
int height;
- GskGLImage image;
+ guint texture_id;
int unused_pixels; /* Pixels of rects that have been used at some point,
But are now unused. */
};
typedef struct _GskGLTextureAtlas GskGLTextureAtlas;
+struct _GskGLTextureAtlases
+{
+ int ref_count;
+
+ GPtrArray *atlases;
+};
+typedef struct _GskGLTextureAtlases GskGLTextureAtlases;
+
+GskGLTextureAtlases *gsk_gl_texture_atlases_new (void);
+GskGLTextureAtlases *gsk_gl_texture_atlases_ref (GskGLTextureAtlases *atlases);
+void gsk_gl_texture_atlases_unref (GskGLTextureAtlases *atlases);
+
+void gsk_gl_texture_atlases_begin_frame (GskGLTextureAtlases *atlases);
+gboolean gsk_gl_texture_atlases_pack (GskGLTextureAtlases *atlases,
+ int width,
+ int height,
+ GskGLTextureAtlas **atlas_out,
+ int *out_x,
+ int *out_y);
+
void gsk_gl_texture_atlas_init (GskGLTextureAtlas *self,
int width,
int height);
void gsk_gl_texture_atlas_free (GskGLTextureAtlas *self);
+void gsk_gl_texture_atlas_realize (GskGLTextureAtlas *self);
+
void gsk_gl_texture_atlas_mark_unused (GskGLTextureAtlas *self,
int width,
int height);